/*
 * @Author: wangjun haodreams@163.com
 * @Date: 2024-11-05 22:32:47
 * @LastEditors: wangjun haodreams@163.com
 * @LastEditTime: 2025-05-05 16:59:24
 * @FilePath: \golib\easyjs\easyjs.go
 * @Description: 这个包比js包更合理更科学
 */
package easyjs

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"

	"gitee.com/haodreams/golib/logs"
	"github.com/dop251/goja"
)

var jsDir = "js/"

func SetJsDir(dir string) {
	if !strings.HasSuffix(dir, "/") {
		dir += "/"
	}
	jsDir = dir
	logs.Info("SET javascript path:", dir)
}

func GetJsDir() string {
	return jsDir
}

type Namer interface {
	Name() string
}

type Exporter interface {
	Export() string
}

// 需要用这个替换已有的
func RunScript(vm *goja.Runtime, name string, src string) (goja.Value, error) {
	buf := bytes.NewBufferString(src)
	newBuf := bytes.NewBuffer(nil)
	for line, err := buf.ReadString('\n'); err == nil || len(line) > 0; line, err = buf.ReadString('\n') {
		if strings.HasPrefix(line, "import ") {
			newBuf.WriteString("// ")
		}
		newBuf.WriteString(line)
	}
	vm.Set("__filename", name) //注入文件名
	//src = strings.ReplaceAll(src, "import * as", "// import * as")
	return vm.RunScript(name, newBuf.String())
}

// 注册一个对象
// 性能测试结果看 go的对象比js对象性能高
func Register(vm *goja.Runtime, objs ...any) {
	for _, obj := range objs {
		typ := reflect.TypeOf(obj)
		name := typ.String()

		name = strings.ToLower(name)
		if n, ok := obj.(Namer); ok {
			name = n.Name()
		}

		idx := strings.LastIndexByte(name, '.')
		if idx >= 0 {
			//logs.Info(idx, name[idx+1:])
			name = name[idx+1:]
		}
		vm.Set(name, obj)
	}
}

// YYYY/MM/DD HH:mm:ss
// 初始化js需要的组件
func Init(vm *goja.Runtime) {
	//注册console组件
	console := vm.NewObject()
	console.Set("trace", func(v any) string {
		if v != nil {
			return Stack(vm) + fmt.Sprint(v)
		}
		return Stack(vm)
	})
	console.Set("log", func(v ...any) {
		logs.LiteInfo(Stack(vm), v...)
	})
	console.Set("info", func(v ...any) {
		logs.LiteInfo(Stack(vm), v...)
	})
	console.Set("warn", func(v ...any) {
		logs.LiteWarn(Stack(vm), v...)
	})
	console.Set("error", func(v ...any) {
		logs.LiteError(Stack(vm), v...)
	})
	console.Set("print", func(v ...any) {
		//logs.LiteError(Stack(vm), v...)
		fmt.Println(v...)
	})
	vm.Set("console", console)

	vm.Set("ExportObject", Exports)

	//注册加载库
	vm.Set("require", func(name string) goja.Value {
		data, err := os.ReadFile(GetJsDir() + name)
		if err != nil {
			vm.Interrupt(err)
			return nil
		}
		_, err = RunScript(vm, name, string(data))
		if err != nil {
			vm.Interrupt(err)
			return nil
		}
		name = filepath.Base(name)
		ext := filepath.Ext(name)
		if ext != "" {
			name = strings.TrimSuffix(name, ext)
		}

		return vm.Get(name)
	})

	//注册时间模块
	vm.Set("dayjs", func(arg any, fmt string) *Dayjs {
		dayjs := new(Dayjs)
		dayjs.ms = ParseTime(arg, fmt)
		return dayjs
	})

	//自动注册系统和编码转换模块
	Register(vm, &OS{}, &FS{}, &Iconv{})
}

func Stack(vm *goja.Runtime) string {
	stacks := make([]goja.StackFrame, 2)
	ss := vm.CaptureCallStack(2, stacks)
	i := len(ss) - 1
	if i < 0 {
		return ""
	}
	pos := ss[i].Position()
	if pos.Line > 0 {
		if pos.Filename != "" {
			return fmt.Sprintf("%s:%d ", pos.Filename, pos.Line)
		}
		return fmt.Sprintf("main.js:%d ", pos.Line)
	}
	return ""
}

func expors(buf *bytes.Buffer, v reflect.Value, have *bool) {
	//导出属性名
	t := v.Type()
	v = reflect.Indirect(v)
	t1 := t
	if t.Kind() == reflect.Ptr {
		t1 = t.Elem()
	}
	if t1.Kind() == reflect.Interface {
		return
	}

	for i := 0; i < t1.NumField(); i++ {
		ft := t1.Field(i)
		field := v.Field(i)
		if ft.Anonymous {
			expors(buf, field, have)
			continue
		}

		name := ft.Name
		if name == "" {
			continue
		}
		if !(name[0] >= 'A' && name[0] <= 'Z') {
			continue
		}

		if field.CanInterface() {
			val := fmt.Sprint(field.Interface())
			if val == "" {
				val = "null"
			} else if strings.HasPrefix(val, "map") {
				val = "{}"
			}
			fmt.Fprintf(buf, "export const %s = %s;\n", name, val)
		} else {
			fmt.Fprintf(buf, "export const %s = null;\n", name)
		}
	}

	//导出方法名
	for i := 0; i < t.NumMethod(); i++ {
		method := t.Method(i)

		name := method.Name
		if !(name[0] >= 'A' && name[0] <= 'Z') {
			continue
		}

		fmt.Fprintf(buf, "export function %s (", name)
		for j := 0; j < method.Type.NumIn(); j++ {
			if j == 0 {
				continue
			}

			param := method.Type.In(j).String()
			pos := strings.LastIndexByte(param, '.')
			if pos >= 0 {
				param = param[pos+1:]
			}
			param = ConverJsType(param)
			if j == 1 {
				fmt.Fprintf(buf, "%s_%d", param, j)
			} else {
				fmt.Fprintf(buf, ",%s_%d", param, j)
			}
		}
		buf.WriteString("){return null};\n")
	}
}

func Exports(obj any) string {
	buf := bytes.NewBufferString("// Code generated by jun. DO NOT EDIT.\n")
	// 获取结构体的类型
	v := reflect.ValueOf(obj)

	name := v.Type().Elem().Name()
	name = strings.ToLower(name)
	if n, ok := obj.(Namer); ok {
		name = n.Name()
	}
	fmt.Fprintf(buf, "// import * as %s from './lib/%s.js'\n\n", name, name)

	have := false
	expors(buf, v, &have)

	if e, ok := obj.(Exporter); ok {
		buf.WriteString(e.Export())
	}
	buf.WriteString("\n")

	dir := jsDir + "lib/"
	_, err := os.Stat(dir)
	if err != nil {
		os.MkdirAll(dir, 0755)
	}
	if runtime.GOOS != "android" {
		os.WriteFile(dir+strings.ToLower(name)+".js", buf.Bytes(), 0644)
	}

	return buf.String()
}

func ConverJsType(t string) string {
	switch t {
	case "interface {}":
		return "any"
	case "map[string]string":
		return "any"
	}
	if strings.Contains(t, "interface") {
		return "any"
	}
	t = strings.ReplaceAll(t, "[]", "Arr")
	return t
}
